home *** CD-ROM | disk | FTP | other *** search
/ MacFormat España 15 / macformat_15.iso / Shareware Internet / Desarrolladores / gray image 2.1 / README < prev    next >
Text File  |  1995-07-30  |  16KB  |  397 lines

  1.             Gray-scale image processing library
  2.  
  3. ***** For the version history, read on
  4.  
  5. ***** You'll need libserv.a, an "advanced" C++ iostream classlib,
  6. to compile and use this grayimage library
  7.  
  8. That iostream code has been posted on comp.sources.misc and info-mac
  9. (info-mac:/dev/lib/advanced-io-cpp.hqx) and is also available from
  10.        ftp://replicant.csci.unt.edu/pub/oleg/c++advio.shar
  11.        ftp://replicant.csci.unt.edu/pub/oleg/c++advio.cpt.hqx (Mac version)
  12. The Mac version is identical to the UNIX version, but includes CW projects
  13. and a compiled library (for a PowerMac).
  14.  
  15. ***** Verification files:  vimage vrectangle vimage_io vfilter
  16.                vmorph_filter vfractal_map
  17.  
  18. Don't forget to compile and run them, see comments in the Makefile for
  19. details.  The verification code checks to see that all the functions
  20. in this package have compiled and run well. The code can also serve as
  21. an example how package classes/functions can be used.  Sample test
  22. pictures are not distributed with the .shar archive to save space. So,
  23. to run vimage_io.cc, you need to make a subdirectory 'pictures' and
  24. put there any pgm/xwd/tiff file you'd like to be used in the test
  25. (name the file as 512.xwd though, or modify vimage_io.cc). At any
  26. rate, you can get a sample file from
  27.     ftp://replicant.csci.unt.edu/pub/oleg/512.xwd.gz
  28.  
  29. ***** Highlights and idioms
  30.  
  31. Elementary pixel operations: assigning/subtracting/adding etc a value
  32. to all pixels, comparing every pixel with a value,
  33. assigning/adding/subtracting/comparing two images, computing image
  34. extrema, various norms and "scalar" products
  35.  
  36.     IMAGE im1(256,256,8); IMAGE im2(im1);
  37.     im1 = 1; im2 = im1; im1 *= 4;
  38.     assert( im1 == 4 ); assert( im1 > 0 );
  39.     im1.invert(); im1.clear(); im2 <<= 2; im1 = 5; 
  40.     im1 &= 0xfe;  assert( im1==im2 );
  41.     im1.clear(); im1 -= im2; im2 = im1;
  42.     assert( im1 * im1 == norm_2_sqr(im1) );
  43.  
  44.  
  45. Accessing square or rectangular parts of an image without much fuss
  46. (and without moving a lot of stuff around)
  47.  
  48.     im1 = 2*pattern; im2 = pattern;
  49.     rowcol rightbottom(im1.q_nrows()-1,im1.q_ncols()-1);
  50.     rowcol center(im1.q_nrows()/2,im1.q_ncols()/2);
  51.                     // Modifying the pixels only within
  52.                     // the lower left quadrant
  53.     im1.rectangle(center,rightbottom) -= 2*pattern;
  54.     assert( !(im1 == 2*pattern) && !(im1 != 2*pattern) );
  55.  
  56.  
  57. Image i/o: supports reading and writing PGM, XWD and Group G
  58. (grayscale) TIFF file formats with automatical recognition of the
  59. input image file format
  60.                     // Note the "extended" file name
  61.     IMAGE raw_image("zcat ../pictures/lenna.tiff.Z |");
  62.     IMAGE image = raw_image.square_of(256,rowcol(120,100));
  63.     image.write_tiff("/tmp/ior","Original image");
  64.     image.display("Image to display");
  65.     image.write_pgm("| xv -");    // another way to display an image
  66.  
  67. Squeezing/stretching and coercing images. Coercing means that one can
  68. assign one image to another no matter what their dimensions are.  The
  69. source image would be shrunk/stretched to fit. Any dimension ratios
  70. are possible, absolutely any
  71.  
  72.     Test_image = pattern;
  73.     IMAGE blown_out(IMAGE::Expand,Test_image);
  74.     IMAGE blown_shrunk(IMAGE::Shrink,blown_out);
  75.     assert( blown_shrunk == Test_image );
  76.  
  77.     IMAGE shrunk(Test_image.q_nrows()/3+1,
  78.              Test_image.q_ncols()/2,Test_image.q_depth());
  79.     shrunk.coerce(Test_image);
  80.     IMAGE vert_stretched(Test_image.q_nrows()+7,
  81.                  Test_image.q_ncols(),Test_image.q_depth());
  82.     vert_stretched.coerce(Test_image);
  83.         assert( vert_stretched.rectangle(rowcol(0,0),
  84.                          rowcol(Test_image.q_nrows()-1,
  85.                         Test_image.q_ncols()-1))
  86.         == Test_image );
  87.  
  88. Note that the last operation involves an implicit conversion from a
  89. rectangle to an image
  90.  
  91.  
  92. Advanced pixel operations via PixelAction. This is the most natural
  93. (and efficient!) way of doing a "sweeping" image processing (that is,
  94. an operation that is going to involve any pixel in some systematic
  95. way).  Thus, instead of writing a loop over all image rows and
  96. columns, one merely needs to specify what action is to be performed on
  97. a current pixel. The package would take care of the iteration, which
  98. is more efficient than a for() loop. The iteration walks through all
  99. the pixels in a row-by-row fashion; one can use this knowledge if one
  100. wishes to. PixelAction would tell you the location of the pixel being
  101. accessed/modified, while PixelPrimAction won't (if it doesn't
  102. matter). The latter is faster, of course.
  103.  
  104. For example, the following snippet squares all image pixels and
  105. verifies that
  106.  
  107.   {
  108.     IMAGE im(Test_image); IMAGE im1(im);
  109.     im = pattern;
  110.     im.square_of(size,rowcol(0,0)) = -1;
  111.     im1 = im;
  112.     struct SqrImage : public PixelPrimAction {
  113.       void operation(GRAY& pixel) { pixel = sqr((GRAY_SIGNED)pixel); }
  114.     };
  115.     im1.apply(SqrImage());
  116.     assert( sum_over((Rectangle)im1) == im.norm_2_sqr() );
  117.   }
  118.  
  119. Still, lookup tables are better for that purpose (see below). In the
  120. next example, which makes a pin-striped image, the location of the
  121. current pixel _is_ important:
  122.  
  123.     class MakeVStripes : public PixelAction
  124.     {
  125.       GRAY pattern;
  126.       void operation(GRAY& pixel) { pixel = col & 1 ? pattern : 0; }
  127.       public: MakeVStripes(const GRAY _p) : pattern(_p) {}
  128.     };
  129.     Test_image.apply(MakeVStripes(pattern));
  130.  
  131. Finally, the following snippet of the verification code checks out to
  132. see that the pixel actions are indeed executed row-by-row. One
  133. iterator is used to assign to each pixel its own offset from the
  134. beginning of the image, and the other iterator checks it.
  135.  
  136.   {
  137.     cout << "Check to see that PixelAction are executed row-wise" << endl;
  138.     class assign_pixels : public PixelAction {
  139.       const card test_im_nrows, test_im_ncols;
  140.       void operation(GRAY& pixel)
  141.       {
  142.     assert(nrows == test_im_nrows);
  143.     assert(ncols == test_im_ncols);
  144.     pixel = col + row*ncols;
  145.       }
  146.     public: assign_pixels(const IMAGE& im) : 
  147.       test_im_nrows(im.q_nrows()), test_im_ncols(im.q_ncols()) {}
  148.     }
  149.     Test_image.apply(assign_pixels(Test_image));
  150.  
  151.     class check_pixels : public PixelPrimAction {
  152.       GRAY curr_offset;
  153.       void operation(GRAY& pixel) { assert(pixel == curr_offset++); }
  154.       public: check_pixels(void) : curr_offset(0) {}
  155.     }
  156.     Test_image.apply(check_pixels());
  157.   }
  158.  
  159.  
  160.  
  161. Lazy images: instead of returning an object return a "recipe" how to
  162. make it. The full image would be rolled out only when and where it's
  163. needed:
  164.     IMAGE map = FractalMap(order);
  165.  
  166. FractalMap is a *class*, not a simple function. However similar this
  167. looks to a returning of an object, it's dramatically
  168. different. FractalMap() constructs a LazyImage, an object of just a
  169. few bytes long. A special "IMAGE(const LazyImage& recipe)" constructor
  170. follows the recipe and makes the fractal map right in place. No pixel
  171. is moved whatsoever!
  172.  
  173. Since the FractalMap is a class, it can be subclassed to modify the
  174. default behavior (say, to override the default uniform noise generator
  175. with a gaussian noise generator, which tends to produce better looking
  176. clouds)
  177.  
  178.   class GaussNoise : public FractalMap
  179.   {
  180.   public:
  181.     GaussNoise(const card order, const Seeds& seeds,
  182.            const bits_per_pixel=8)
  183.       : FractalMap(order,seeds,bits_per_pixel) {}
  184.     inline int get_noise(const card scale) const {
  185.       long sum = 0;
  186.       for(register int i=0; i<12; i++)
  187.     sum += rand();            // keep the result within 
  188.       return (scale * (sum-(6<<15)))>>17; }    // [-scale/2,scale/2]
  189.   };
  190.  
  191.   IMAGE map = type == 0 ? 
  192.       (LazyImage&)FractalMap(order,FractalMap::Seeds(sul,sll,sur,slr)) :
  193.       (LazyImage&)GaussNoise(order,FractalMap::Seeds(sul,sll,sur,slr));
  194.  
  195. This technique is particularly useful when one needs to construct an
  196. image in some particular way (say, by reading it from a file/database,
  197. or by decoding/decompressing, etc) and return it. Thus, instead of
  198. returning an image, one should always return a LazyImage.
  199.  
  200.     cout << "\tExpansion of the uniform image with a small stain\n";
  201.     Test_image = pattern;
  202.     Test_image(0,0) = 1;
  203.     Test_image(1,1) = 0;
  204.     IMAGE blown_out(IMAGE::Expand,Test_image);
  205.     class BlowImage : public LazyImage
  206.     {
  207.       const IMAGE& orig_image;
  208.       void fill_in(IMAGE& im) const
  209.       {
  210.     for(register int i=0; i<im.q_nrows(); i++)
  211.       for(register int j=0; j<im.q_ncols(); j++)
  212.         im(i,j) = orig_image(i/2,j/2);
  213.       }
  214.     public:
  215.       BlowImage(const IMAGE& image) : 
  216.     LazyImage(2*image.q_nrows(),2*image.q_ncols(),image.q_depth()),
  217.         orig_image(image) {}
  218.     };
  219.     IMAGE another_blown_out = BlowImage(Test_image);
  220.     assert( another_blown_out == blown_out );
  221.  
  222.  
  223.  
  224. Image filtration. The package supports a whole bunch of various image
  225. filtration techniques: convolutional, median, and morphological.  They
  226. are *very* optimized, and *very* fast. All filtration is done
  227. in-place.  My experience tells that it takes noticeably more time to
  228. read an image than to filter it.  It's possible to apply a
  229. convolution/median filter to rows only, or to columns only, or to both
  230. rows and columns.  A convolution kernel can have either int or
  231. rational coefficients; in the latter case, the denominator can be
  232. specified as an exact power of two, or as just any integer. Note, for
  233. a computer, a floating point number is the rational
  234. number. Convolution algorithms are optimized to all these particular
  235. cases (and to the generic case, too).
  236.  
  237.  
  238.     Test_image = vert_line;
  239.     verify_identity(FilterIt(Test_image).
  240.             conv(conv_kernel(1,2,1),FilterIt::Columns)>>=2,vert_line);
  241.     verify_identity(FilterIt(Test_image).
  242.             conv_col(conv_kernel(1,2,3,CommonDenom(6))),vert_line);
  243.     verify_identity(FilterIt(Test_image).
  244.             conv(conv_kernel(0,2,0,over_2_up(1))),vert_line);
  245.     expected = 0;
  246.     expected.rectangle(rowcol(0,vert_line.q_ncols()/2-1),
  247.             rowcol(vert_line.q_nrows()-1,
  248.                    vert_line.q_ncols()/2+1)) = -seed;
  249.     verify_identity(FilterIt(Test_image=vert_line).
  250.             conv(conv_kernel(1,1,1),FilterIt::Rows),expected);
  251.  
  252. The following (intentionally contrived) snippet from the verification
  253. code performs a phase shift of an image through filtration
  254.  
  255.     expected = small_sq(0,0);
  256.     expected.square_of(2,rowcol(sq_row-1,sq_col-1)) =
  257.       small_sq.square_of(2,rowcol(sq_row,sq_col));
  258.     verify_identity(FilterIt(Test_image=small_sq).
  259.             conv(conv_kernel(0,0,2,over_2_up(1))),expected);
  260.  
  261. The package supports median filtration with window sizes of three and
  262. five.  The filtration can be done by rows only, by columns only, or by
  263. rows _and_ by columns. The latter is equivalent to a 2D median
  264. filtration with a diamond-shaped window.
  265.  
  266.     verify_identity(FilterIt(Test_image).median(FilterIt::RowsAndColumns,3),
  267.             expected);
  268.     verify_identity(FilterIt(Test_image).median(FilterIt::RowsAndColumns,5),
  269.             expected);
  270.  
  271.  
  272. Lookup table substitutions are very powerful and very fast. Indeed, in
  273. a typical 512x512 8-bit deep grayscale picture, there are 1/4 M pixels
  274. but only 256 possible pixel values. Therefore, if one needs to perform
  275. some pixel calculation
  276.     new_pixel(i,j) = f(old_pixel(i,j))
  277. the fastest way of accomplishing this task is to create a look-up table,
  278. fill it in like lookup(i) = f(i), i=0..255, and then do
  279.     FilterIt(image).translate(lookup,LookupT::CoerceFringes);
  280. to substitute all "old pixels" of an 'image' with the "new_pixel"
  281. values. The second argument, LookupT::CoerceFringes or
  282. LookupT::LeaveFringes determines what to do with the pixels (if any)
  283. that fall outside of the lookup table range. Note, a lookup table can
  284. have any number of entries, even 1.
  285.  
  286. The following example changes all pixels with value 2 to value 3:
  287.  
  288.     LookupT map(LookupT::MapTo(2,3));
  289.     assert( FilterIt(Test_image=diverse_image).
  290.       translate(map,LookupT::LeaveFringes) != 2 );
  291.  
  292. (the assert statement makes sure that there is no pixel with value 2
  293. left in the translated image).
  294.  
  295. a slight modification
  296.     verify_pixel_value(FilterIt(Test_image=diverse_image).
  297.             translate(map,LookupT::CoerceFringes),3);
  298.  
  299. makes all pixels have the same value, three. Aren't lookup tables
  300. powerful, or what?
  301.  
  302. There are many ways to create lookup tables: allocate a blank table
  303. and fill it in, create an Identity lookup table for a particular image
  304. depth and modify a few entries, or perform a composition of existing
  305. lookup tables.
  306.  
  307. Note
  308.     FilterIt(image).translate(lookup,LookupT::CoerceFringes);
  309. returns a reference to an image after the substitution, so one can
  310. use it in chains like
  311.     assert( FilterIt(Test_image=diverse_image).
  312.       translate(map,LookupT::LeaveFringes) != seed );
  313.  
  314.  
  315. ***** Grand plans
  316.  
  317. - a PixelAction-like class for lookup table elements
  318. - in PixelAction, a method 'operation(GRAY& pixel)' should return bool
  319.   (if it returns false, the traversal is terminated). Maybe there should
  320.   be another class, like PixelControlledAction (which also would have
  321.   specifications as to where to start and where/when to end the traversal),
  322.   OR traversal classes for Rectangles
  323. - make operation() method of PixelAction non-virtual, and make IMAGE::apply()
  324.   templated to a PixelAction-like class (when gcc starts supporting member
  325.   function templates)
  326. - make IMAGE::allocate() virtual, or define a class ImageData that contains
  327.   only the pixel matrix without indices. So when one needs to handle an
  328.   array of images (frames or color-separation planes), the memory won't be
  329.   wasted on many (mostly redundant) indices
  330. - add spans (by generalizing rowcol's), 1D, 2D spans, maybe even regions (non-
  331.   rectangular spans). Add IMAGE constructors from spans. Use spans
  332.   as generalized indices to take slices of an image
  333. - add a LazyMask to achieve
  334.     B(A==5) = 4;
  335.   (that is, for all i,j; if(A(i,j) ==5) B(i,j) = 4; ), like in Matlab.
  336. - simple color imagery with transformations among various tristimulus
  337.   representations
  338. - support addition of a rectangle to an image or an image to a rectangle,
  339.   or comparison between a rectangle and an image
  340. - add convolutions with a 5-point kernel and with 3x3 (non-separable) kernels
  341. - add a "non-deterministic averaging" filtering (like in LIFE, only
  342.   survival of a pixel is probabilistic, and the chance of survival depends
  343.   on how much a pixel sticks out of its neighborhood)
  344.  
  345.  
  346. ***** Revision history
  347.  
  348. version 2.0
  349. Makefile is much more user-friendly.
  350. Introduced card (typedef'ed as unsigned int) for row and col dimensions,
  351. indices and other quantities which are always non-negative.
  352. Added PixelAction and PixelPrimAction, classes to do a specific (maybe
  353. very complex) operation on every pixel regardless of its position
  354. (PixelPrimAction) or with regard to its position (PixelAction). The image
  355. is traversed row-by-row.
  356. Introduced LazyImage (which is what to return instead of IMAGE).
  357. bool is used when appropriate.
  358. Removed GNU extensions, code is made very portable.
  359. Sundry of optimizations.
  360. IMAGE::operator(): now it returns GRAY (the pixel itself) in const contexts,
  361. otherwise, it returns a reference to a pixel.
  362. read_xwd/read_pgm use "new style" with the PixelAction stuff.
  363. Median filtration re-worked; can be done now only by rows, only by cols,
  364. and both by Rows and columns. It's heavily optimized and must be very fast.
  365. Added convolutions: by rows, by cols, and separable 2D. Very efficient:
  366. kernel coeffs can be int, rational with power2 common denom, and
  367. rational. The three cases are handled separately (yet uniformly) and very
  368. efficiently (almost as much as one can get). Filtering is done "in-place"
  369. Added assignments with stretching/squeezing an image to fit (IMAGE::coerce()),
  370. the stretching/squeezing is done by arbitrary ratio! (the sizes of the
  371. original and the assigned image can be anything, not necessarily int).
  372. Added lookup table translations.
  373.  
  374. version 1.15, Feb 8, 1995
  375. Cosmetic changes to please gcc 2.6.3 and adjust to the new version
  376. of endian_io.h
  377.  
  378. version 1.14, Mar 24, 1994 (previously posted on comp.sources.misc)
  379. Added support for reading/writing PGM and TIFF image file formats
  380. (in addition to the existed support for XWD format). Default write
  381. is in XWD format.
  382. Added image histogram equalization.
  383. Generalizing the Square_area class to the Rectangle class, which handles
  384. arbitrary rectangular areas of the image.
  385. Added comparison predicates that check a relation between an int and all
  386. pixels of the image.
  387. Added Abs() for putting down negative pixels, and application of a generic
  388. user-supplied function to every pixel of the image.
  389. Added +, -, *, >>, and << operations on rowcol (returning rowcol)
  390. Added assignments, comparison, and arithmetics (offsetting) on objects of
  391. the class rowcol.
  392. Added finding Extrema pixel values and image normalization for display.
  393.  
  394. version 1.1, Apr 3, 1992
  395. Initial revision
  396.  
  397.